<template>
    <div id="body"></div>
</template>
<script>

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min';

/*场景，渲染器，镜头，背景星星，帧率器，第一人称控制*/
let scene, renderer, camera, particleSystem, control;
let Sun,
    Mercury,  //水星
    Venus,  //金星
    Earth,
    Mars,
    Jupiter, //木星
    Saturn, //土星
    Uranus, //天王
    Neptune, //海王
    stars = [];
const cameraFar = 3000;  //镜头视距

export default {
    data() {
        return {
            ThreeEngine: null,
        }
    },
    mounted() {

        /*renderer*/
        //渲染器
        renderer = new THREE.WebGLRenderer({ alpha: true })
        renderer.physicallyCorrectLights = true;
        renderer.shadowMap.enabled = true; //辅助线
        renderer.shadowMapSoft = true; //柔和阴影
        renderer.setClearColor('black', 1);
        renderer.setSize(window.innerWidth, window.innerHeight)
        document.getElementById('body').appendChild(renderer.domElement);

        /*scene*/
        scene = new THREE.Scene();

        /*camera*/
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, cameraFar);
        camera.position.set(-200, 50, 0);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
        scene.add(camera);

        control = new OrbitControls(camera, renderer.domElement)
        control.update()

        /*sun skin pic*/
        // const texture = new THREE.TextureLoader().load( 'textures/land_ocean_ice_cloud_2048.jpg' );
        let sunSkinPic = new THREE.TextureLoader().load(new URL('../assets/sunCore.jpg', import.meta.url), {}, () => {
            renderer.render(scene, camera);
        });

        /*sun*/
        Sun = new THREE.Mesh(new THREE.SphereGeometry(12, 16, 16),
            new THREE.MeshLambertMaterial({
                /*color: 0xffff00,*/
                emissive: 0xdd4422,
                map: sunSkinPic
            })
        );
        Sun.name = 'Sun';
        Sun.castShadow = false;
        Sun.receiveShadow = false;
        scene.add(Sun);
        /*opacity sun*/
        let opSun = new THREE.Mesh(new THREE.SphereGeometry(14, 16, 16),
            new THREE.MeshLambertMaterial({
                side: THREE.DoubleSide,
                color: 0xff0000,
                /*emissive: 0xdd4422,*/
                transparent: true,
                opacity: .35
            })
        );

        opSun.name = 'Sun';
        scene.add(opSun);

        const plane = new THREE.Mesh(new THREE.PlaneGeometry(500, 500),
            new THREE.MeshStandardMaterial({
                side: THREE.DoubleSide,
                color: 0xffffff,
                metalness: .7,
                roughness: 0.5,
                // emissive: 0xffffff,
            })
        );
        plane.rotateX(-Math.PI / 2)
        plane.position.y = -30
        // plane.castShadow = true;
        plane.receiveShadow = true;
        scene.add(plane)

        //gui
        const gui = new GUI()

        /*planets*/
        Mercury = this.initPlanet('Mercury', 0.02, 0, 'rgb(124,131,203)', 20, 2);
        stars.push(Mercury);

        Venus = this.initPlanet('Venus', 0.012, 0, 'rgb(190,138,44)', 30, 4);
        stars.push(Venus);

        Earth = this.initPlanet('Earth', 0.010, 0, 'rgb(46,69,119)', 40, 5);
        stars.push(Earth);

        Mars = this.initPlanet('Mars', 0.008, 0, 'rgb(210,81,16)', 50, 4);
        stars.push(Mars);

        Jupiter = this.initPlanet('Jupiter', 0.006, 0, 'rgb(254,208,101)', 70, 9);
        stars.push(Jupiter);

        Saturn = this.initPlanet('Saturn', 0.005, 0, 'rgb(210,140,39)', 100, 7, {
            color: 'rgb(136,75,30)',
            innerRedius: 9,
            outerRadius: 11
        });
        stars.push(Saturn);

        Uranus = this.initPlanet('Uranus', 0.003, 0, 'rgb(49,168,218)', 120, 4);
        stars.push(Uranus);

        Neptune = this.initPlanet('Neptune', 0.002, 0, 'rgb(84,125,204)', 150, 3);
        stars.push(Neptune);



        //环境光
        let ambient = new THREE.AmbientLight(0x999999, 3);
        scene.add(ambient);

        /*太阳光*/
        let sunLight = new THREE.PointLight(0xddddaa, 6500, 500);
        sunLight.castShadow = true;
        sunLight.position.set(0, 0)
        // sunLight.scale.set(25,25,25)
        opSun.add(sunLight);
        let sunAmbient = new THREE.AmbientLight(0x999999, 3);
        opSun.add(sunAmbient)
        sunLight.shadow.mapSize.set(2048, 2048)

        /*背景星星*/
        const particles = 20000;  //星星数量
        /*buffer做星星*/
        let bufferGeometry = new THREE.BufferGeometry();

        let positions = new Float32Array(particles * 3);
        let colors = new Float32Array(particles * 3);

        let color = new THREE.Color();

        const gap = 1000; // 定义星星的最近出现位置

        for (let i = 0; i < positions.length; i += 3) {

            // positions

            /*-2gap < x < 2gap */
            let x = (Math.random() * gap * 2) * (Math.random() < .5 ? -1 : 1);
            let y = (Math.random() * gap * 2) * (Math.random() < .5 ? -1 : 1);
            let z = (Math.random() * gap * 2) * (Math.random() < .5 ? -1 : 1);

            /*找出x,y,z中绝对值最大的一个数*/
            let biggest = Math.abs(x) > Math.abs(y) ? Math.abs(x) > Math.abs(z) ? 'x' : 'z' :
                Math.abs(y) > Math.abs(z) ? 'y' : 'z';

            let pos = { x, y, z };

            /*如果最大值比n要小（因为要在一个距离之外才出现星星）则赋值为n（-n）*/
            if (Math.abs(pos[biggest]) < gap) pos[biggest] = pos[biggest] < 0 ? -gap : gap;

            x = pos['x'];
            y = pos['y'];
            z = pos['z'];

            positions[i] = x;
            positions[i + 1] = y;
            positions[i + 2] = z;

            // colors

            /*70%星星有颜色*/
            let hasColor = Math.random() > 0.3;
            let vx, vy, vz;

            if (hasColor) {
                vx = (Math.random() + 1) / 2;
                vy = (Math.random() + 1) / 2;
                vz = (Math.random() + 1) / 2;
            } else {
                vx = 1;
                vy = 1;
                vz = 1;
            }

            /*let vx = ( Math.abs(x) / n*2 ) ;
            var vy = ( Math.abs(y) / n*2 ) ;
            var vz = ( Math.abs(z) / n*2 ) ;*/

            color.setRGB(vx, vy, vz);

            colors[i] = color.r;
            colors[i + 1] = color.g;
            colors[i + 2] = color.b;
        }

        bufferGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        bufferGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        bufferGeometry.computeBoundingSphere();

        /*星星的material*/
        let material = new THREE.PointsMaterial({ size: 6, vertexColors: THREE.VertexColors });
        particleSystem = new THREE.Points(bufferGeometry, material);
        scene.add(particleSystem);


        //帮助模块
        const axesHelper = new THREE.AxesHelper(500)
        scene.add(axesHelper)
        axesHelper.visible = false;
        const lightHelper = new THREE.PointLightHelper(sunLight, 1, 100);
        scene.add(lightHelper);
        lightHelper.visible = false;
        plane.visible = false;

        
        gui.add(Sun.position, 'y').name('太阳Y轴').min(-100).max(100).onChange((val) => {
            opSun.position.y = val;
        })
        gui.add(sunLight.position, 'y').name('光源Y轴').min(-100).max(100).onChange((val) => {
            sunLight.position.y = val;
        })
        gui.add(sunAmbient, 'intensity').name('太阳环境光强度').min(0).max(10)
        gui.add(sunLight, 'intensity').name('太阳点光照强度').min(0).max(20000)
        gui.add(sunLight, 'distance').name('太阳点光照距离').min(0).max(20000)
        gui.add(sunLight, 'decay').name('太阳点光照衰减').min(0).max(5)

        gui.add(plane, 'visible').name('显示平面')
        gui.add(axesHelper, 'visible').name('显示坐标轴')
        gui.add(lightHelper, 'visible').name('显示光源辅助')


        //渲染动画
        let run = { run: true };
        gui.add(run, 'run')
        const animate = () => {
            if (run.run) {
                /*行星公转*/
                stars.forEach(star =>
                    this.moveEachStar(star)
                )
                Sun.rotation.y = (Sun.rotation.y == 2 * Math.PI ? 0.0008 * Math.PI : Sun.rotation.y + 0.0008 * Math.PI);
            }
            requestAnimationFrame(animate)
            renderer.render(scene, camera)
            lightHelper.update()
        }
        requestAnimationFrame(animate)


    },
    methods: {
        /*每一颗行星的公转*/
        moveEachStar(star) {

            star.angle += star.speed;
            if (star.angle > Math.PI * star.distance) {
                star.angle -= Math.PI * star.distance;
            }

            star.Mesh.position.set(star.distance * Math.sin(star.angle), 0, star.distance * Math.cos(star.angle));

            /*碎星带*/
            if (star.ring) {
                star.ring.position.set(star.distance * Math.sin(star.angle), 0, star.distance * Math.cos(star.angle));
            }
        },
        initPlanet(name, speed, angle, color, distance, volume, ringMsg) {
            let mesh = new THREE.Mesh(new THREE.SphereGeometry(volume, 16, 16),
                new THREE.MeshStandardMaterial({
                    color, metalness: .7,
                    roughness: 0.5,
                })
            );
            mesh.position.x = distance;
            mesh.receiveShadow = true;
            mesh.castShadow = true;

            mesh.name = name;

            /*轨道*/
            let track = new THREE.Mesh(new THREE.RingGeometry(distance - 0.2, distance + 0.2, 64, 1),
                new THREE.MeshBasicMaterial({ color: 0x888888, side: THREE.DoubleSide })
            );
            track.rotation.x = - Math.PI / 2;
            scene.add(track);

            let star = {
                name,
                speed,
                angle,
                distance,
                volume,
                Mesh: mesh
            }

            /*如果有碎星带*/
            if (ringMsg) {
                let ring = new THREE.Mesh(new THREE.RingGeometry(ringMsg.innerRedius, ringMsg.outerRadius, 32, 6),
                    new THREE.MeshBasicMaterial({ color: ringMsg.color, side: THREE.DoubleSide, opacity: .7, transparent: true })
                );

                ring.name = `Ring of ${name}`;
                ring.rotation.x = - Math.PI / 3;
                ring.rotation.y = - Math.PI / 4;
                scene.add(ring);

                star.ring = ring;
            }


            scene.add(mesh);

            return star;
        },
    }
}
</script>

<style scoped lang="less">
.three-canvas {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: #d6eaff;
}
</style>